package com.hzw.saas.common.storage.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hzw.saas.common.storage.enums.PathTypeEnum;
import com.hzw.saas.common.util.PathUtils;
import com.hzw.saas.common.util.constants.PathConsts;
import com.hzw.saas.common.util.enums.ConflictModeEnum;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 存储工具
 * jdk 1.7+
 *
 * @author zzl
 * @since 04/20/2021
 */
@Slf4j
public final class StorageHubUtil {

    /**
     * 路径是否都为目录
     *
     * @param paths 待检查路径
     * @return true-都为目录; false-含有文件
     */
    public static boolean isAllFolders(String... paths) {
        Path path = Paths.get("", PathUtils.normalizePaths(paths));
        while (path != null) {
            if (PathUtil.isFile(path, false)) {
                return false;
            }
            path = path.getParent();
        }
        return true;
    }

    /**
     * 断言文件存在
     * 不存在抛出IORuntimeException（包裹FileNotFoundException）
     *
     * @param file 待检查文件
     */
    public static void assertExist(File file) {
        if (FileUtil.exist(file))
            return;
        log.debug("The file/folder not exists: {}", file.toString());
        throw new IORuntimeException(new FileNotFoundException(file.toString()));
    }

    /**
     * 断言文件不存在
     * 存在抛出IORuntimeException（包裹FileAlreadyExistsException）
     *
     * @param file 待检查文件
     */
    public static void assertNotExist(File file) {
        if (!FileUtil.exist(file))
            return;
        log.debug("The file/folder already exists: {}", file.toString());
        throw new IORuntimeException(new FileAlreadyExistsException(file.toString()));
    }

    /**
     * 断言文件相同
     *
     * @param srcFile 源文件
     * @param tarFile 目标文件
     */
    public static void assertEqual(File srcFile, File tarFile) {
        boolean eq = FileUtil.equals(srcFile, tarFile);
        Assert.isTrue(eq, "Files '{}' and '{}' are not equal", srcFile.toString(), tarFile.toString());
    }

    /**
     * 断言文件不相同
     *
     * @param srcFile 源文件
     * @param tarFile 目标文件
     */
    public static void assertNotEqual(File srcFile, File tarFile) {
        boolean eq = FileUtil.equals(srcFile, tarFile);
        Assert.isFalse(eq, "Files '{}' and '{}' are equal", srcFile.toString(), tarFile.toString());
    }

    /**
     * 计算目录(递归子文件)或文件的总大小
     *
     * @param file 目录或文件
     * @return 总大小(byte), -1: file为null或不存在
     */
    public static long size(File file) {
        if (Objects.isNull(file) || !file.exists() || FileUtil.isSymlink(file)) {
            return -1;
        }
        return FileUtil.size(file);
    }

    /**
     * 计算文件摘要
     *
     * @param file       文件
     * @param digestType 摘要类型, sha1, sha256, md5(默认)
     * @return 信息摘要, null: 文件不存在
     */
    public static String calcDigest(File file, String digestType) {
        if (!FileUtil.isFile(file)) {
            return StrUtil.EMPTY;
        }
        if (DigestAlgorithm.SHA1.getValue().equalsIgnoreCase(digestType)) {
            return DigestUtil.sha1Hex(file);
        }
        if (DigestAlgorithm.SHA256.getValue().equalsIgnoreCase(digestType)) {
            return DigestUtil.sha256Hex(file);
        }
        return DigestUtil.md5Hex(file);
    }

    /**
     * 计算数据摘要
     *
     * @param data       数据
     * @param digestType 摘要类型, sha1, sha256, md5(默认)
     * @return 信息摘要
     */
    public static String calcDigest(byte[] data, String digestType) {
        if (data == null) {
            return StrUtil.EMPTY;
        }
        if (DigestAlgorithm.SHA1.getValue().equalsIgnoreCase(digestType)) {
            return DigestUtil.sha1Hex(data);
        }
        if (DigestAlgorithm.SHA256.getValue().equalsIgnoreCase(digestType)) {
            return DigestUtil.sha256Hex(data);
        }
        return DigestUtil.md5Hex(data);
    }

    /**
     * 名称冲突时重命名
     *
     * @param file 待重命名文件
     * @return 新名称
     */
    public static File renameConflictName(File file) {
        File folder = FileUtil.getParent(file, 1);
        String prefix = FileUtil.getPrefix(file);
        String suffix = FileUtil.getSuffix(file);
        // 处理文件名(不含后缀)，去除可能存在的copy后缀
        prefix = removeDuplicateNameSuffix(prefix);
        suffix = addDotBeforeSuffix(suffix);
        int index = 1;
        File newFile;
        do {
            String newName = prefix + duplicateNameSuffix(index) + suffix;
            newFile = new File(folder, newName);
            index++;
        } while (newFile.exists());
        return newFile;
    }

    /**
     * @param srcFile 待检查的文件
     * @param mode    冲突解决模式
     * @param srcType 源文件类型
     * @param touch   是否新建已重命名的文件/目录，用来占位名称空间
     * @return 原文件或冲突时重命名后的文件
     */
    public static File doConflictName(File srcFile, PathTypeEnum srcType, boolean touch, ConflictModeEnum mode) {
        if (srcFile == null)
            return null;
        File existFile = srcFile;
        if (FileUtil.exist(existFile)) {
            PathTypeEnum existType = existFile.isFile() ? PathTypeEnum.FILE : PathTypeEnum.FOLDER;
            String existTypeText = existType.name().toLowerCase();
            switch (mode) {
                case RENAME_SRC:
                    srcFile = StorageHubUtil.renameConflictName(srcFile);
                    log.info("The {} with the same name exist in path ({}), rename: {} -> {}",
                        existTypeText, srcFile.getParent(), existFile.getName(), srcFile.getName());
                    break;
                case ALLOW:
                case THROW_ERR:
                case DELETE_SRC:
                case DELETE_SRC_PURE:
                    log.debug("The {} with the same name exist: {}, throw FileAlreadyExistsException",
                        existTypeText, existFile);
                    throw new IORuntimeException(new FileAlreadyExistsException(existFile.toString()));
                case COVER_TAR:
                case COVER_TAR_PURE:
                    if (!Objects.equals(existType, srcType)) {
                        if (srcFile.isFile()) {
                            throw new IORuntimeException(new FileAlreadyExistsException("file with the same name exist: " + srcFile));
                        } else {
                            throw new IORuntimeException(new FileAlreadyExistsException("folder with the same name exist: " + srcFile));
                        }
                    }
                    log.info("The {} with the same name exist({}), it will be covered by the new one", existTypeText, srcFile);
                    break;
            }
        }
        if (touch) {
            switch (srcType) {
                case FILE:
                    return FileUtil.touch(srcFile);
                case FOLDER:
                    return FileUtil.mkdir(srcFile);
            }
        }
        return srcFile;
    }

    /**
     * 静默删除
     *
     * @param file 待删除文件
     */
    public static void silentDelete(File file) {
        try {
            PathUtil.del(file.toPath());
        } catch (Exception e) {
            log.error("静默删除失败", e);
        }
    }

    public static void silentDelete(String path) {
        try {
            PathUtil.del(Paths.get(path));
        } catch (Exception e) {
            log.error("静默删除失败", e);
        }
    }

    public static void checkTarLength(File file, Long expectedLength, boolean del) {
        if (expectedLength == null)
            return;
        long length = StorageHubUtil.size(file);
        if (Objects.equals(expectedLength, length)) {
            log.debug("verify file length, passed: {file: {}, length: {}}", file, length);
            return;
        }
        if (del) {
            silentDelete(file);
        }
        String errorMsg = String.format("file length mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkTarDigest(File file, String expectedDigest, String digestType, boolean del) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageHubUtil.calcDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest)) {
            log.debug("verify file digest, passed: {file: {}, digest: {}}", file, digest);
            return;
        }
        if (del) {
            silentDelete(file);
        }
        String errorMsg = String.format("file digest mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcLength(File file, Long expectedLength) {
        if (expectedLength == null)
            return;
        long length = StorageHubUtil.size(file);
        if (Objects.equals(expectedLength, length))
            return;
        String errorMsg = String.format("source file length mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcLength(byte[] file, Long expectedLength) {
        if (expectedLength == null)
            return;
        long length = file.length;
        if (Objects.equals(expectedLength, length))
            return;
        String errorMsg = String.format("source bytes length mismatch: {expected: %s, actual: %s}",
            expectedLength, length);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcDigest(File file, String expectedDigest, String digestType) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageHubUtil.calcDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest))
            return;
        String errorMsg = String.format("source file digest mismatch: {file: %s,expected: %s, actual: %s}",
            file.getPath(), expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }

    public static void checkSrcDigest(byte[] file, String expectedDigest, String digestType) {
        if (StrUtil.isBlank(expectedDigest))
            return;
        String digest = StorageHubUtil.calcDigest(file, digestType);
        if (Objects.equals(expectedDigest, digest))
            return;
        String errorMsg = String.format("source bytes digest mismatch: {expected: %s, actual: %s}",
            expectedDigest, digest);
        throw new IORuntimeException(errorMsg);
    }

    /******************************************私有工具方法*****************************************************/

    /**
     * 重名文件后缀
     *
     * @param index 第几个重名文件
     * @return 新名称
     */
    private static String duplicateNameSuffix(int index) {
        return String.format(PathConsts.COPY_SUFFIX_FORMAT, index);
    }

    /**
     * 去除重名文件后缀
     *
     * @param name
     * @return
     */
    private static String removeDuplicateNameSuffix(String name) {
        if (StrUtil.isBlank(name))
            return name;
        Pattern pattern = Pattern.compile(PathConsts.COPY_SUFFIX_PATTERN, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(name);
        return matcher.find() ? matcher.group(1) : name;
    }

    private static String addDotBeforeSuffix(String suffix) {
        if (StrUtil.isNotBlank(suffix) && !suffix.startsWith("."))
            suffix = "." + suffix;
        return suffix;
    }

    /**
     * Suppress default constructor
     */
    private StorageHubUtil() {
        throw new UnsupportedOperationException("Private constructor, cannot be accessed.");
    }
}
